
/* GCSx
** IMPORTIMG.CPP
**
** Import image (creating tileset/fontset) dialog
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

// Import image dialog

ImportImgDialog* ImportImgDialog::dialogFontSet = NULL;
ImportImgDialog* ImportImgDialog::dialogTileSet = NULL;

ImportImgDialog* ImportImgDialog::createFontSet() { start_func
    if (!dialogFontSet) {
        dialogFontSet = new ImportImgDialog(1);
    }
    return dialogFontSet;
}

ImportImgDialog* ImportImgDialog::createTileSet() { start_func
    if (!dialogTileSet) {
        dialogTileSet = new ImportImgDialog(0);
    }
    return dialogTileSet;
}

void ImportImgDialog::destroy() { start_func
    if (dialogFontSet) {
        delete dialogFontSet;
        dialogFontSet = NULL;
    }
    if (dialogTileSet) {
        delete dialogTileSet;
        dialogTileSet = NULL;
    }
}

ImportImgDialog::ImportImgDialog(int font) : Dialog(font ? "Import Image as Font" : "Import Image as Image/Tile Set") { start_func
    initialized = 0;
    isFont = font;
    image = NULL;

    height = width = TileSet::TILE_DEFAULT_SIZE;
    left = top = horiz = vert = 0;
    skipBlank = skipDupe = importScene = 0;
}

ImportImgDialog::~ImportImgDialog() { start_func
    if (image) SDL_FreeSurface(image);
}

void ImportImgDialog::run(WorldEdit* world, Window* srcWin) { start_func
    if (!initialized) {
        Widget* w = NULL;
        Widget* tr = new WStatic(ID_LABEL, "Transparency:");

        w = new WStatic(ID_LABEL, "\tImage File:");
        w->addTo(this);

        w = new WTextBox(ID_FILE, &filename, 0);
        w->disable();
        w->addTo(this);
        
        w = new WButton(ID_FILECHOOSE, "\tChoose...", BUTTON_NOTHING);
        w->addTo(this);

        arrangeRow(1, tr->getWidth(), 1);

        w = new WStatic(ID_LABEL, "Preview:");
        w->addTo(this);
        
        w = new WSplitPreview(ID_PREVIEW, &bkColor, &image, 200, 100);
        w->addTo(this);

        arrangeRow(1, tr->getWidth(), 1);
        
        tr->addTo(this);
        
        w = new WShowColor(ID_TRANSPARENCY, 30, 30, &bkColor);
        w->setToolTip("Click on preview to select transparency color");
        w->addTo(this);

        w = new WCheckBox(ID_SKIPBLANK, "Skip \tblank tiles", &skipBlank, 1);
        w->setToolTip("Don't import tiles that are entirely transparent or black");
        w->addTo(this);

        arrangeRow(1, tr->getWidth(), 1);

        w = new WStatic(ID_LABEL, "\tTile size:");
        w->addTo(this);

        w = new WNumberBox(ID_WIDTH, &width, MIN_TILESIZE, MAX_TILESIZE);
        w->addTo(this);

        w = new WStatic(ID_LABEL, "wide;");
        w->addTo(this);

        w = new WNumberBox(ID_HEIGHT, &height, MIN_TILESIZE, MAX_TILESIZE);
        w->addTo(this);

        w = new WStatic(ID_LABEL, "high");
        w->addTo(this);

        arrangeRow(1, tr->getWidth(), 1);

        w = new WStatic(ID_LABEL, "\tPadding:");
        w->addTo(this);

        w = new WNumberBox(ID_LEFT, &left, 0, 999);
        w->setToolTip("Skip this many pixels on the left of the image");
        w->addTo(this);

        w = new WStatic(ID_LABEL, "left;");
        w->addTo(this);

        w = new WNumberBox(ID_TOP, &top, 0, 999);
        w->setToolTip("Skip this many pixels on the top of the image");
        w->addTo(this);

        w = new WStatic(ID_LABEL, "top");
        w->addTo(this);

        arrangeRow(1, tr->getWidth(), 1);

        w = new WStatic(ID_LABEL, "\tSpacing:");
        w->addTo(this);

        w = new WNumberBox(ID_HORIZ, &horiz, 0, 99);
        w->setToolTip("Skip this many pixels between tiles");
        w->addTo(this);

        w = new WStatic(ID_LABEL, "horizontal;");
        w->addTo(this);

        w = new WNumberBox(ID_VERT, &vert, 0, 99);
        w->setToolTip("Skip this many pixels between tiles");
        w->addTo(this);

        w = new WStatic(ID_LABEL, "vertical");
        w->addTo(this);

        arrangeRow(1, tr->getWidth(), 1);

        w = new WStatic(ID_LABEL, blankString);
        w->addTo(this);

        w = new WCheckBox(ID_SKIPDUPE, "Skip \tduplicate tiles", &skipDupe, 1);
        w->setToolTip("Don't import tiles that are exact duplicates");
        w->addTo(this);

        arrangeRow(1, tr->getWidth());

        if (!isFont) {
            w = new WStatic(ID_LABEL, blankString);
            w->addTo(this);

            w = new WCheckBox(ID_IMPORTSCENE, "C\treate tiled scene from image", &importScene, 1);
            w->setToolTip("Creates a new one-layer scene using the new tileset, based on the image");
            w->addTo(this);

            arrangeRow(1, tr->getWidth());
        }

        w = new WButton(ID_OK, messageBoxOK, BUTTON_OK);
        w->addTo(this);

        w = new WButton(ID_CANCEL, messageBoxCancel, BUTTON_CANCEL);
        w->addTo(this);

        makePretty();

        initialized = 1;
    }
    
    // Only reset filename, image, and bk color from last import
    bkColor.max = bkColor.maxAlpha = (1 << 8) - 1;
    bkColor.R = bkColor.G = bkColor.B = bkColor.A = 0;
    bkColor.RGBtoHSL();
    
    filename = blankString;
    if (image) SDL_FreeSurface(image);
    image = NULL;

    // Hit OK?
    if (runModal() == ID_OK) {
        if (image) {
            // Import now!
            TileSetEdit* myTileSet = new TileSetEdit(NULL, isFont, 0);
            SceneEdit* myScene = NULL;
            LayerEdit* myLayer = NULL;
            if (importScene) {
                myScene = new SceneEdit(NULL, 0);
                myLayer = new LayerEdit(NULL, myScene, 0);
                // Note that layer isn't actually added to scene yet
            }
            if (doImport(myTileSet, myScene, myLayer, world)) {
                myTileSet->setInfo(world, world->unusedTileSetId());
                world->importTileSet(myTileSet, 1, srcWin);
                if (importScene) {
                    myScene->setInfo(world, world->unusedSceneId());
                    myLayer->setInfo(world, world->unusedLayerId());
                    // @TODO: can't be before setinfo, as needs world to work
                    // but here, it triggers undo which is wrong
                    myLayer->setTileset(myTileSet->getId());
                    world->importScene(myScene, 1, srcWin);
                }
            }
            else {
                delete myLayer;
                delete myScene;
                delete myTileSet;
            }
        }
        else {
            guiErrorBox("No file was selected to import", errorTitleImport);
        }
    }

    // Free up some memory
    if (image) SDL_FreeSurface(image);
    image = NULL;
}

int ImportImgDialog::doImport(TileSetEdit* ts, SceneEdit* sc, LayerEdit* sl, WorldEdit* world) { start_func
    assert(image);
    
    // Work areas
    SDL_Surface* workArea = createSurface32(width, height);
    int pitch = workArea->pitch / 4;
    pitch -= width;
    Uint32* layerWork = NULL;
    
    // Size of source
    int sW = image->w;
    int sH = image->h;
    
    // Determine number of images we'd like (don't import partial tiles)
    int countW = (sW - left + horiz) / (width + horiz);
    int countH = (sH - top + vert) / (height + vert);

    if (countW < 1) {
        guiErrorBox("Image is not wide enough to import requested tile width", errorTitleImport);
        return 0;
    }

    if (countH < 1) {
        guiErrorBox("Image is not tall enough to import requested tile height", errorTitleImport);
        return 0;
    }
    
    int surfaceW, surfaceH, perLine; // We don't need these, but the next function needs references
    int numTiles = countW * countH;
    if (numTiles > MAX_TILES) numTiles = MAX_TILES;
    numTiles = TileSetEdit::calculateTileSurfaceSizing(width, height, numTiles, surfaceW, surfaceH, perLine);
    
    // If we're doing dupe checking, count total number of dupe checks
    long long dupeChecks = 0;
    long long totalProgress = 0;
    if (skipDupe) dupeChecks = ((long long)numTiles - 1) * (long long)numTiles / 2;
    
    // Initialize progress meter
    ProgressMeter* progress = new ProgressMeter(numTiles + dupeChecks);
        
    // Prep tileset/layer
    ts->markLock(); // @TODO: throw_File
    ts->setSize(width, height, numTiles, 0);
    if (sl) {
        assert(sc);
        sl->setType(Layer::LAYER_TILE);
        sl->setSize(countW, countH);
        sl->setExtended(0);
        sl->setEffects(0);
        layerWork = new Uint32[countW * countH];
        sc->markLock(); // @TODO: throw_File
    }
    
    // Start at...
    int sX = left;
    int sY = top;
        
    // Target tile
    int tTile = 1;
    
    // What tile we should be on if no dupes; also used for layer
    long ghostTile = 1;
    
    // Transparency
    Uint32 trans = mapColor32(bkColor.R, bkColor.G, bkColor.B, bkColor.A);
    Uint32 blank = mapColor32(0, 0, 0, 0);
    int anyBlank = 0; // Any transparent areas?

    // For dupe checking
    int cPitch = ts->viewTilePitch() / 4;
    cPitch -= width;
    
    while (tTile <= numTiles) {
        // Pull one tile
        blit(sX, sY, image, 0, 0, workArea, width, height);

        // Update transparency and check for all black/blank        
        Uint32* point = (Uint32*)workArea->pixels;
        int allBlack = skipBlank ? 1 : 0;
        int allBlank = skipBlank ? 1 : 0;
        for (int y = 0; y < height; ++y) {
            for (int x = 0; x < width; ++x) {
                // Transparency 
                if (*point == trans) *point = blank;
                    
                // All alpha 0 or black?
                int R, G, B, A;
                splitColor32(*point, R, G, B, A);
                if ((R != 0) || (G != 0) || (B != 0)) allBlack = 0;
                if (A != 0) allBlank = 0;
                if (A == 0) anyBlank = 1;
                
                ++point;
            }
            point += pitch;
        }
        
        // Save tile?
        if ((!allBlank) && (!allBlack)) {
            int duplicated = 0;
            
            // Compare to all previously imported tiles to find duplicates?
            if ((skipDupe) && (tTile > 1)) {
                // Scan each existing tile
                int dupe;
                for (dupe = 1; dupe < tTile; ++dupe) {
                    // Two points to compare
                    Uint32* point = (Uint32*)workArea->pixels;
                    const Uint32* check = (Uint32*)ts->viewTileData(dupe);
                    int same = 1;
                    
                    // Scan entire tile
                    for (int y = 0; y < height; ++y) {
                        for (int x = 0; x < width; ++x) {
                            // Stop as soon as something is different
                            if (*point != *check) {
                                same = 0;
                                break;
                            }
                            ++point;
                            ++check;
                        }
                        if (!same) break;
                        point += pitch;
                        check += cPitch;
                    }
                    
                    progress->updateProgress(++totalProgress);

                    if (same) {
                        duplicated = dupe;
                        // (so value of dupe is consistently the same
                        // whether we exit normally or shortcut here)
                        ++dupe;
                        break;
                    }
                }
                
                // Update meter for skipped checks
                if (dupe < ghostTile)
                    totalProgress += ghostTile - dupe;
            }
            else if (skipDupe) {
                // Update meter for skipped dupe checks
                totalProgress += ghostTile - 1;
            }
            
            if (!duplicated) {
                if (sl) layerWork[ghostTile - 1] = tTile | Layer::LAYER_TILE_DEFAULT;
                ts->saveTile(tTile++, workArea, NULL);
            }
            else if (sl) layerWork[ghostTile - 1] = duplicated | Layer::LAYER_TILE_DEFAULT;
        }
        else {
            if (sl) layerWork[ghostTile - 1] = Layer::LAYER_TILE_DEFAULT;
            // Update meter for skipped dupe checks
            if (skipDupe) totalProgress += ghostTile - 1;
        }
        
        progress->updateProgress(++totalProgress);
        ++ghostTile;

        // Next tile
        sX += width + horiz;
        if (sX + width > sW) {
            sX = left;
            sY += height + vert;
            if (sY + height > sH) break;
        }
    }
    
    // Adjust size if less tiles (error if 0)
    --tTile;
    --ghostTile;
    // (don't waste time resizing if we're just gonna delete it)
    if ((tTile < numTiles) && (tTile >= 1)) ts->setSize(width, height, tTile, 0);
    progress->doneProgress();
    
    if (tTile < 1) {
        guiErrorBox("No non-blank tiles were found to import", errorTitleImport);
        ts->markUnlock();
        if (sl) sc->markUnlock();
        delete[] layerWork;
        return 0;
    }
    
    if (ghostTile < countW * countH) {
        guiErrorBox("Too many tiles- not all possible tiles were imported", errorTitleImport);
        if (sl)
            memSet32(layerWork + ghostTile, Layer::LAYER_TILE_DEFAULT, countW * countH - ghostTile);
    }
        
    // Name
    vector<string> splitName;
    splitFilename(filename.c_str(), splitName);
    string findName(splitName[splitName.size() - 1]);
    string lFindName = findName;
    toLower(lFindName);
    int pos = 1;
    while (world->findTileSet(lFindName)) {
        findName = splitName[splitName.size() - 1];
        findName += " ";
        findName += intToStr(++pos);
        lFindName = findName;
        toLower(lFindName);
    }
    ts->setName(findName);
    
    // Other settings
    ts->setTilesPerLine(tTile == ghostTile ? countW : 0);
    ts->setDefaultTransparent(anyBlank);
    ts->markUnlock();

    // Layer/scene
    if (sl) {
        // Layer load, name
        Rect rect;
        rect.x = rect.y = 0;
        rect.w = countW;
        rect.h = countH;
        sl->saveLayer(layerWork, NULL, NULL, countW, rect, NULL);
        sl->setName(splitName[splitName.size() - 1]);
        
        // Scene name
        findName = splitName[splitName.size() - 1];
        lFindName = findName;
        toLower(lFindName);
        pos = 1;
        while (world->findScene(lFindName)) {
            findName = splitName[splitName.size() - 1];
            findName += " ";
            findName += intToStr(++pos);
            lFindName = findName;
            toLower(lFindName);
        }
        sc->setName(findName);
        sc->markUnlock();
        
        sc->insertLayer(sl, 0);
    }

    delete[] layerWork;
    return 1;
}

void ImportImgDialog::childModified(Window* modified) { start_func
    Dialog::childModified(modified);
    
    WShowColor* wColor = NULL;
    WSplitPreview* wImg = NULL;
    Widget* widget = dynamic_cast<Widget*>(modified);

    if (widget) {
        switch (widget->getId()) {
            case ID_WIDTH:
            case ID_HEIGHT:
            case ID_LEFT:
            case ID_TOP:
            case ID_HORIZ:
            case ID_VERT:
                widget->apply();
                wImg = dynamic_cast<WSplitPreview*>(findWidget(ID_PREVIEW));
                wImg->changeStats(width, height, top, left, horiz, vert);
                break;

            case ID_PREVIEW:
                widget->apply();
                wColor = dynamic_cast<WShowColor*>(findWidget(ID_TRANSPARENCY));
                wColor->load();
                break;

            case ID_TRANSPARENCY:
                widget->apply();
                wImg = dynamic_cast<WSplitPreview*>(findWidget(ID_PREVIEW));
                wImg->load();
                break;
        }
    }
}

Dialog::ButtonAction ImportImgDialog::verifyEntry(int buttonId, Dialog::ButtonAction buttonType) { start_func
    if (buttonId == ID_FILECHOOSE) {
        try {
            if (fileOpen(FILETYPE_IMAGE, 0, filename)) {
                WTextBox* fn = dynamic_cast<WTextBox*>(findWidget(ID_FILE));
                fn->load();

                if (image) SDL_FreeSurface(image);
                image = NULL;
                image = IMG_Load(filename.c_str());
                if (image == NULL) {
                    throw VideoException("Error loading image: %s", SDL_GetError());
                }
                else {
                    SDL_SetAlpha(image, 0, 255);
                    WSplitPreview* wImg = dynamic_cast<WSplitPreview*>(findWidget(ID_PREVIEW));
                    wImg->load();
                }
            }
        }
        catch (FileException& e) {
            guiErrorBox(string(e.details), errorTitleFile);
        }
    }
    
    return Dialog::verifyEntry(buttonId, buttonType);
}

WSplitPreview::WSplitPreview(int pId, color* pSetting, SDL_Surface* const* src, int myWidth, int myHeight) : Widget(pId, blankString, pSetting) { start_func
    assert(src);

    source = src;
    split = NULL;
    dragMode = WSPLITPREVIEW_DRAG_NONE;

    topPad = leftPad = 0;
    horizSplit = vertSplit = 0;
    tileWidth = tileHeight = TileSet::TILE_DEFAULT_SIZE;

    changeSize(myWidth, myHeight);
}

WSplitPreview::~WSplitPreview() { start_func
    if (split) SDL_FreeSurface(split);
}

void WSplitPreview::changeSize(int newWidth, int newHeight) { start_func
    displayW = newWidth;
    displayH = newHeight;

    if ((newWidth <= 0) || (newHeight <= 0)) {
        resize(0, 0);
    }
    else {
        resize(newWidth + PREVIEW_BORDER_SIZE * 2, newHeight + PREVIEW_BORDER_SIZE * 2);
    }
}

void WSplitPreview::changeStats(int width, int height, int top, int left, int horiz, int vert) { start_func
    topPad = top;
    leftPad = left;
    horizSplit = horiz;
    vertSplit = vert;
    tileWidth = width;
    tileHeight = height;
    load();
}

void WSplitPreview::setBk(int x, int y) { start_func
    if (split) {
        splitColor32(getPixel(x, y, split), bkColor.R, bkColor.G, bkColor.B, bkColor.A);
        bkColor.RGBtoHSL();
        parent->childModified(this);
    }
}

int WSplitPreview::event(int hasFocus, const SDL_Event* event) { start_func
    assert(event);
    assert(parent);

    switch (event->type) {
        case SDL_MOUSEBUTTONDOWN:
        case SDL_MOUSEBUTTONDBL:
            if (event->button.button == SDL_BUTTON_LEFT) {
                dragMode = WSPLITPREVIEW_DRAG_NONE;
                
                if ((event->button.y >= PREVIEW_BORDER_SIZE) && (event->button.y < height - PREVIEW_BORDER_SIZE)) {
                    setBk(event->button.x - PREVIEW_BORDER_SIZE, event->button.y - PREVIEW_BORDER_SIZE);
                    dragMode = WSPLITPREVIEW_DRAG_BK;
                }

                return 1;
            }
            break;

        case SDL_MOUSEBUTTONUP:
            if (event->button.button == SDL_BUTTON_LEFT) {
                dragMode = WSPLITPREVIEW_DRAG_NONE;
                return 1;
            }
            break;

        case SDL_MOUSEMOTION:
            if (event->motion.state & SDL_BUTTON_LMASK) {
                if (dragMode == WSPLITPREVIEW_DRAG_BK) {
                    setBk((Sint16)event->motion.x - PREVIEW_BORDER_SIZE, (Sint16)event->motion.y - PREVIEW_BORDER_SIZE);
                }

                return 1;
            }
            else if ((event->motion.y >= PREVIEW_BORDER_SIZE) &&
                (event->motion.y < height - PREVIEW_BORDER_SIZE) &&
                (event->motion.x >= PREVIEW_BORDER_SIZE) &&
                (event->motion.x < width - PREVIEW_BORDER_SIZE)) {
                selectMouse(MOUSE_PIXEL);
            }
            else {
                selectMouse(MOUSE_NORMAL);
            }
            break;
    }

    return 0;
}

void WSplitPreview::load() { start_func
    bkColor = *((color*)setting);
    if ((*source == NULL) || (displayW <= 0) || (displayH <= 0)) {
        if (split) SDL_FreeSurface(split);
        split = NULL;
    }
    else {
        SDL_Surface* src = *source;
        if (split) SDL_FreeSurface(split);
        split = NULL;
        split = createSurface32(displayW, displayH);

        // Start at...
        int sX = leftPad;
        int sY = topPad;
        
        // Target placement
        int tX = 0;
        int tY = 0;
        
        do {
            // Pull one tile
            blit(sX, sY, src, tX, tY, split, tileWidth, tileHeight);
            
            // Next tile
            tX += tileWidth + TILE_SPACING;
            if (tX >= displayW) {
                tX = 0;
                tY += tileHeight + TILE_SPACING;
                if (tY >= displayH) break;
                sX = leftPad;
                sY += tileHeight + vertSplit;
            }
            else {
                sX += tileWidth + horizSplit;
            }
        } while (1);
    }

    setDirty();
}

void WSplitPreview::apply() { start_func
    *((color*)setting) = bkColor;
}

void WSplitPreview::display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset) { start_func
    assert(destSurface);

    if (visible) {
        // If dirty, redraw all
        if (dirty) {
            getRect(toDisplay);
            toDisplay.x += xOffset;
            toDisplay.y += yOffset;
            dirty = 0;
            intersectRects(toDisplay, clipArea);
        }

        // Anything to draw?
        if (toDisplay.w) {
            SDL_SetClipRect(destSurface, &toDisplay);

            xOffset += x;
            yOffset += y;

            // We specifically use black, except for "white" copy
            SDL_FillRect(destSurface, &toDisplay, SDL_MapRGB(destSurface->format, 0, 0, 0));
            // @TODO: Corners of inverted box aren't grey
            if (PREVIEW_BORDER_SIZE) drawGuiBoxInvert(xOffset, yOffset, width, height, PREVIEW_BORDER_SIZE, destSurface);
            if (split) blit(0, 0, split, xOffset + PREVIEW_BORDER_SIZE, yOffset + PREVIEW_BORDER_SIZE, destSurface);
        }
    }
}

